/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package programa;

import java.util.*;
import Model.*;

/**
 *
 * @author manelv8
 */
public class Principal {

    public static ArrayList<BloqueioInfo> tabelaBloqueio;
    static ArrayList<Operacao> scheduleResultado;
    static ArrayList<Operacao> listOperacoesEspera;
    static final int st_liberado = 1;
    static final int st_aguardando = 2;
    static Tabela tabelaX;
    static Tabela tabelaY;
    

    public static void main(String[] args) {

        geraArvore();

        scheduleResultado = new ArrayList<Operacao>();
        tabelaBloqueio = new ArrayList<BloqueioInfo>();
        listOperacoesEspera = new ArrayList<Operacao>();

      //  String schedule = "RD:1:X:R , WR:2:R11:R , WR:3:Y:P , CT:1 , CT:2 , CT:3 ";
        
   /* com dead lock*/ 
        //String schedule = " UD:1:R10:R , RD:2:X:P , RD:1:R6:R , RD:2:R6:R, WR:1:R6:R, WR:2:R6:R , UD:2:X:P , WR:2:R5:R , WR:1:R10:R , RD:2:X:R , CT:2 , CT:1";
        
 //       String schedule = " UD:1:R10:R , RD:2:X:P , RD:1:R6:R , UD:2:X:P , WR:2:R5:R , WR:1:R10:R , RD:2:X:R , CT:2 , CT:1";
          String schedule = " RD:1:R1:R , WR:2:R3:R , RD:2:R11:R, WR:3:Y:R , CT:1 , CT:2 , CT:3  ";
   schedule = schedule.trim();
          ArrayList<Operacao> operacoes = getListaOperacoes(schedule);

        for (Operacao o : operacoes) {

            alocaOperacao(o);
        }

            
        
        System.out.println("\n\n TABELA BOQUEIO:");
        for (BloqueioInfo bloqueio : tabelaBloqueio) {
            System.out.println(bloqueio);
        }

System.out.println("\n\n Schedule resultado:");
        for (Operacao o : scheduleResultado) {
            System.out.println(o);
        }




    }

    private static ArrayList<Operacao> getListaOperacoes(String schedule) {
        ArrayList<Operacao> retorno = new ArrayList<Operacao>();

        String[] operacoes = schedule.split(",");
        for (String ops : operacoes) {
            String[] campos = ops.split(":");
            String op0 = campos[0].trim();
            Operacao o;
            if (op0.equals("CT")) {
                o = new Operacao(op0, campos[1].trim());
            } else {
                o = new Operacao(campos[0].trim(), campos[1].trim(), campos[2].trim(), Granularidade.getGran(campos[3].trim()));
            }

            retorno.add(o);
        }


        return retorno;
    }
  
    private static boolean alocaOperacao(Operacao operacao) {
    System.out.println("\n\n\n Tentando alocar  "+ operacao.toString());
        int tipoBloqueio = 0;
        boolean commit = false;

        if (operacao.getAcao().equals("RD")) {
            tipoBloqueio = Bloqueio.RL;
        } else if (operacao.getAcao().equals("UD")) {
            tipoBloqueio = Bloqueio.UL;
        } else if (operacao.getAcao().equals("WR")) {
            tipoBloqueio = Bloqueio.WL;
        } else if (operacao.getAcao().equals("CT")) {
            commit = true;
        }

        if (commit) {
            if(!operacaoEstaEsperando(operacao)){
                         liberaBloqueios(operacao.getTransacaoId());
                         scheduleResultado.add(operacao);
               System.out.println("commit ok");          
                  alocaListaEspera(operacao);
                   
                   
               return true;
            }else{
                listOperacoesEspera.add(operacao);
                System.out.println("commit nao possivel!");
                return false;
            }
        } else {
            ArrayList objetos = getObjetos(operacao);


            boolean compativel = verificaCompatibilidade(operacao, tipoBloqueio, objetos);
      //      BloqueioInfo bloqueio;

            if (compativel) {

                //1- verificar compatibilidade dos intencionais para os ascendentes ok
                // 2 -verificar compatibilidade do objeto de bloqueio ok
                //colocar na tabela de bloqueios de acordo com a granulidade
                //se for tupla colocar todas as tuplas
                System.out.println("é compativel!");
                if(!listOperacoesEspera.contains(operacao)){
                    inserirBloqueioNaLista(operacao,objetos,tipoBloqueio,st_liberado);
                    
                }
                else {
                    System.out.println("atualizando tabela de bloqueios...");
                    atualizaStatusOperacao(operacao,objetos,tipoBloqueio,st_liberado);
                    
                }
                scheduleResultado.add(operacao);
                
                System.out.println("\n\n estado atual TABELA BOQUEIO:");
                for (BloqueioInfo bloqueio : tabelaBloqueio) {
                    System.out.println(bloqueio);
                }
          
                
                
                return true;
                 
            } else {
                if (listOperacoesEspera.contains(operacao)) {
                    System.out.println("nÃO COMPATIVEL!! CONTINUA ESPERANDO...");
                } else {
                    System.out.println("NÃO compativel! VAI PARA ESPERA");
                    inserirBloqueioNaLista(operacao, objetos, tipoBloqueio, st_aguardando);
                    listOperacoesEspera.add(operacao);
                }
                
                return false;
            }

  

        }

        
        
        
        

    }
    /*
     * private static void liberaBloqueios(char transacaoId) { for(Operacao o :
     * tabelaBloqueio) }
     */

    private static boolean verificaCompatibilidade(Operacao operacao, int tipoBloqueio, ArrayList objetos) {
        boolean retorno = true;
        int tipoBloqueioIntencional = tipoBloqueio + 3;//rl =0 + 3 = irl =3 



        for (BloqueioInfo detem : tabelaBloqueio) {

            //  String objeto = "";
            for (Object obj : objetos) {
                if (obj instanceof Tabela) {
                    Tabela tab = (Tabela) obj;
                    //    objeto = tab.getNome();

                    if (!CompatibilidadeTabel.isCompativel(detem, operacao, tipoBloqueio, tab)) {
                        retorno = false;
                    }
                } else if (obj instanceof Tupla) {
                    Tupla tup = (Tupla) obj;

                    /*
                     * verificar a granularidade e : 
                     * 1-se a granularidade for tabela: 
                     * 1.1 - verificar se detem.objeto == tup.tabela, se
                     * for é pq detem é ascendente de tup, chamar iscompativel
                     * passando o tipodebloqueio intencional 
                     * 1.2 - else verificar se detem.objeto == tup.pagina, se for e pq
                     * detem é ascendete de tup, chmar iscompativel passando o
                     * tipodebloqueio intencional
                     * 2-else se a granularidade for pagina 
                     * 2.1- verificar se detem.objeto == tup.pagina idem
                     */

                    if (operacao.getGranularidade() == Granularidade.tabela) {
                        if (detem.objeto.equals(tup.getTabela().getNome())) {
                            if (!CompatibilidadeTabel.isCompativel(detem, operacao, tipoBloqueioIntencional, tup)) {
                                retorno = false;
                            }
                        } else if (detem.objeto.equals(tup.getPage().getPageId())) {
                            if (!CompatibilidadeTabel.isCompativel(detem, operacao, tipoBloqueioIntencional, tup)) {
                                retorno = false;
                            }
                        }
                    } else if (operacao.getGranularidade() == Granularidade.pagina) {
                          if (detem.objeto.equals(tup.getPage().getPageId())) {
                            if (!CompatibilidadeTabel.isCompativel(detem, operacao, tipoBloqueioIntencional, tup)) {
                                retorno = false;
                            }
                        }
                        
                    }



                    if (!CompatibilidadeTabel.isCompativel(detem, operacao, tipoBloqueio, tup)) {
                        retorno = false;
                    }
                }else if (obj instanceof Pagina) {
                    Pagina pag = (Pagina) obj;

                   

                    if (operacao.getGranularidade() == Granularidade.tabela) {
                        if (detem.objeto.equals(pag.getTabela().getNome())) {
                            if (!CompatibilidadeTabel.isCompativel(detem, operacao, tipoBloqueioIntencional, pag)) {
                                retorno = false;
                            }
                        }
                    } 



                    if (!CompatibilidadeTabel.isCompativel(detem, operacao, tipoBloqueio, pag)) {
                        retorno = false;
                    }
                }

            }


            //adicionar aqui depos quais operações não sao compativels para montar o GRAFO DE ESPERA
        }

        return retorno;


    }

    private static void geraArvore() {
        tabelaX = new Tabela();
        tabelaX.setNome("X");
        ArrayList<Pagina> paginasX = new ArrayList<Pagina>();

        tabelaY = new Tabela();
        tabelaY.setNome("Y");
        ArrayList<Pagina> paginasY = new ArrayList<Pagina>();

        Pagina a = new Pagina();
        a.setPageId("P1");
        a.setTabela(tabelaX);
        a.setTuplas(new ArrayList<Tupla>());

        Pagina b = new Pagina();
        b.setPageId("P2");
        b.setTabela(tabelaX);
        b.setTuplas(new ArrayList<Tupla>());

        Pagina c = new Pagina();
        c.setPageId("P3");
        c.setTabela(tabelaY);
        c.setTuplas(new ArrayList<Tupla>());

        Pagina d = new Pagina();
        d.setPageId("P4");
        d.setTabela(tabelaY);
        d.setTuplas(new ArrayList<Tupla>());

        for (int i = 1; i <= 5; i++) {

            a.getTuplas().add(new Tupla("R" + i, tabelaX, a));
            b.getTuplas().add(new Tupla("R" + (i + 5), tabelaX, b));
            c.getTuplas().add(new Tupla("R" + (i + 10), tabelaY, c));
            d.getTuplas().add(new Tupla("R" + (i + 15), tabelaY, d));


        }

        paginasX.add(a);
        paginasX.add(b);
        paginasY.add(c);
        paginasY.add(d);
        tabelaX.setPaginas(paginasX);
        tabelaY.setPaginas(paginasY);
    }

    private static ArrayList getObjetos(Operacao operacao) {
        ArrayList objetos = new ArrayList();

        if (operacao.getGranularidade() == Granularidade.tabela) {
            if (operacao.getObjeto().equals("X")) {
                objetos.add(tabelaX);
            } else if (operacao.getObjeto().equals("Y")) {
                objetos.add(tabelaY);
            } else {
                // se o objeto for uma tupla e a granularidade tabela, vai retornar a TABELA que aquela tupla pertence
                Tupla tupla = retornaTupla(operacao.getObjeto());
                objetos.add(tupla.getTabela());

            }
            //FIM GRANULARIDADE TABELA
            //GRANULARIDADE PAGINA
        } else if (operacao.getGranularidade() == Granularidade.pagina) {
            if (operacao.getObjeto().equals("X")) { //se for a tabela e granularidade pagina
                //retornar paginas de x
                objetos.addAll(tabelaX.getPaginas());

            } else if (operacao.getObjeto().equals("Y")) {
                //retornar paginas de y
                objetos.addAll(tabelaY.getPaginas());
            } else { //se nao vai ser uma tupla
                Tupla tupla = retornaTupla(operacao.getObjeto());
                objetos.add(tupla.getPage());//retorna a pagina passando uma tupla
            }

        } else if (operacao.getGranularidade() == Granularidade.tupla) {
            if (operacao.getObjeto().equals("X")) { //se for a tabela e granularidade tupla
                //retornar tuplas  de x
                objetos.addAll(tabelaX.getPaginas().get(0).getTuplas());
                objetos.addAll(tabelaX.getPaginas().get(1).getTuplas());

            } else if (operacao.getObjeto().equals("Y")) {
                //retornar tuplas de y
                objetos.addAll(tabelaX.getPaginas().get(0).getTuplas());
                objetos.addAll(tabelaX.getPaginas().get(1).getTuplas());
            } else { //se nao vai ser uma tupla
                Tupla tupla = retornaTupla(operacao.getObjeto());
                objetos.add(tupla);
            }
        }

        return objetos;
    }

    private static Tupla retornaTupla(String tupla) {
        for (Pagina px : tabelaX.getPaginas()) {
            for (Tupla tx : px.getTuplas()) {
                if (tx.getId().equals(tupla)) {

                    return tx;
                }

            }

        }

        for (Pagina py : tabelaY.getPaginas()) {
            for (Tupla ty : py.getTuplas()) {
                if (ty.getId().equals(tupla)) {

                    return ty;
                }

            }

        }

        return null;

    }

  
    private static void inserirBloqueioNaLista(Operacao operacao, ArrayList objetos, int tipoBloqueio, int status) {
        int tipoBloqueioIntencional = tipoBloqueio + 3;
        for (Object obj : objetos) {

            if (obj instanceof Tupla) {
                Tupla tupla = (Tupla) obj;

                BloqueioInfo block = new BloqueioInfo(operacao.getTransacaoId(), tupla.getId(), tipoBloqueio, operacao.getGranularidade(), status);
                tabelaBloqueio.add(block);
                 System.out.println("inserindo BLOQUEIO " +block.toString());
                //adiciona intencional na pagina e tabela
                if (status != st_aguardando) {
                    block = new BloqueioInfo(operacao.getTransacaoId(), tupla.getPage().getPageId(), tipoBloqueioIntencional, operacao.getGranularidade(), status);
                    if(!existeBloqueioLista(block)){
                      tabelaBloqueio.add(block);
                      System.out.println("inserindo INTENCIONAL " +block.toString());
                    }
                    block = new BloqueioInfo(operacao.getTransacaoId(), tupla.getTabela().getNome(), tipoBloqueioIntencional, operacao.getGranularidade(), status);
                     if(!existeBloqueioLista(block)){
                    tabelaBloqueio.add(block);
                    System.out.println("inserindo INTENCIONAL " +block.toString());
                     }
                }
            } else if (obj instanceof Pagina) {
                Pagina pagina = (Pagina) obj;

                BloqueioInfo block = new BloqueioInfo(operacao.getTransacaoId(), pagina.getPageId(), tipoBloqueio, operacao.getGranularidade(), status);
                tabelaBloqueio.add(block);
                System.out.println("inserindO BLOQUEIO " +block.toString());
                if (status != st_aguardando) {
                    block = new BloqueioInfo(operacao.getTransacaoId(), pagina.getTabela().getNome(), tipoBloqueioIntencional, operacao.getGranularidade(), status);
                     if(!existeBloqueioLista(block)){
                    tabelaBloqueio.add(block);
                      System.out.println("inserindo INTENCIONAL " +block.toString());
                     }
                }
            } else if (obj instanceof Tabela) {
                Tabela tabela = (Tabela) obj;

                BloqueioInfo block = new BloqueioInfo(operacao.getTransacaoId(), tabela.getNome(), tipoBloqueio, operacao.getGranularidade(), status);
                tabelaBloqueio.add(block);
                System.out.println("inserindO BLOQUEIO " +block.toString());
            }
        }
    }

    private static boolean existeBloqueioLista(BloqueioInfo block) {
     for(BloqueioInfo binfo : tabelaBloqueio){
         if(binfo.transacaoId.equals(block.transacaoId) 
                 && binfo.objeto.equals(block.objeto) 
                 && binfo.tipoBloqueio == block.tipoBloqueio
                 && binfo.granularidade == block.granularidade
                 && binfo.status == block.status)
             return true;
        }
     return false;
    }

    private static boolean operacaoEstaEsperando(Operacao operacao) {
        for(Operacao op : listOperacoesEspera){
            if(op.getTransacaoId().equals(operacao.getTransacaoId()) && !op.getAcao().equals("CT"))
                return true;
        }
        return false;
    }

    private static void liberaBloqueios(String transacaoId) {
        ArrayList<BloqueioInfo> removidos = new ArrayList<BloqueioInfo>();
        for (BloqueioInfo binfo : tabelaBloqueio) {
            if (binfo.transacaoId.equals(transacaoId)) {
                System.out.println("liberando bloqueio: "+ binfo.transacaoId + binfo.objeto+" " +Bloqueio.getStrBloqueio(binfo.tipoBloqueio));
                removidos.add(binfo);
                
            }
            
        }
        tabelaBloqueio.removeAll(removidos);
    }



    private static void atualizaStatusOperacao(Operacao operacao, ArrayList objetos, int tipoBloqueio, int status) {
        Tabela tabela = null;
        Tupla tupla = null;
        Pagina pagina = null;
        for (Object obj : objetos) {
            if (obj instanceof Tabela) {
                tabela = (Tabela) obj;
                for (BloqueioInfo binfo : tabelaBloqueio) {
                    if (binfo.transacaoId.equals(operacao.getTransacaoId())
                            && binfo.objeto.equals(tabela.getNome())
                            && binfo.tipoBloqueio == tipoBloqueio
                            && binfo.granularidade == operacao.getGranularidade()) {
                        binfo.status = status;
                    }

                }


            } else if (obj instanceof Pagina) {
                pagina = (Pagina) obj;
                    for (BloqueioInfo binfo : tabelaBloqueio) {
                    if (binfo.transacaoId.equals(operacao.getTransacaoId())
                            && binfo.objeto.equals(pagina.getPageId())
                            && binfo.tipoBloqueio == tipoBloqueio
                            && binfo.granularidade == operacao.getGranularidade()) {
                        binfo.status = status;
                    }

                }
                

            } else if (obj instanceof Tupla) {
                tupla = (Tupla) obj;
                   for (BloqueioInfo binfo : tabelaBloqueio) {
                    if (binfo.transacaoId.equals(operacao.getTransacaoId())
                            && binfo.objeto.equals(tupla.getId())
                            && binfo.tipoBloqueio == tipoBloqueio
                            && binfo.granularidade == operacao.getGranularidade()) {
                        binfo.status = status;
                    }

                }
                

            }

        }


    }

    private static void alocaListaEspera(Operacao origem) {
        
        ArrayList<Operacao> listOperacao = new ArrayList<Operacao>();
        listOperacao.addAll(listOperacoesEspera);
        
        if(origem.getAcao().equals("CT") && listOperacoesEspera.contains(origem)){
            listOperacoesEspera.remove(origem);
            alocaListaEspera(origem);
        }else{
        
        for(Operacao o : listOperacao){
                
            if(alocaOperacao(o)){
                listOperacoesEspera.remove(o);
            }
            
        }
        
    }
       
    }
}
